package sf.dynamicsql;

import org.mybatis.dynamic.sql.delete.render.DeleteStatementProvider;
import org.mybatis.dynamic.sql.insert.render.BatchInsert;
import org.mybatis.dynamic.sql.insert.render.GeneralInsertStatementProvider;
import org.mybatis.dynamic.sql.insert.render.InsertSelectStatementProvider;
import org.mybatis.dynamic.sql.insert.render.InsertStatementProvider;
import org.mybatis.dynamic.sql.insert.render.MultiRowInsertStatementProvider;
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
import org.mybatis.dynamic.sql.update.render.UpdateStatementProvider;
import sf.common.wrapper.Page;
import sf.database.OrmConfig;
import sf.database.dialect.DBDialect;
import sf.database.jdbc.sql.Crud;
import sf.database.template.CType;
import sf.database.util.DBUtils;
import sf.database.util.SimpleSQLTemplate;
import sf.spring.util.Assert;
import sf.tools.ObjectMapUtils;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Stream;

public class DynmicSQLImpl implements DynmicSQLInf {
    private static DynmicSQLImpl instance = new DynmicSQLImpl();

    public static DynmicSQLImpl getInstance() {
        return instance;
    }

    private DynmicSQLImpl() {

    }

    @Override
    public <T> int[] insertBatch(Connection conn, BatchInsert<T> provider, List<T> records, boolean insertFast,
                                 int batchSize, List<String> pkeys, List<Map<String, Object>> keyValues) {
        String sql = provider.getInsertStatementSQL();
        List<Map<String, Object>> list = new ArrayList<>();
        for (Object record : records) {
            Map<String, Object> parameters = ObjectMapUtils.bean2map(record);
            list.add(parameters);
        }
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        return Crud.getInstance().getCrudTemplate().executeBatch(conn, sql, CType.source, list, insertFast, batchSize, pkeys, keyValues);
    }

    @Override
    public <T> int[] insertBatch(Connection conn, boolean insertFast,
                                 int batchSize, List<String> pkeys, List<Map<String, Object>> keyValues, MultiRowInsertStatementProvider<T> provider) {
        String sql = provider.getInsertStatement();
        List<T> records = provider.getRecords();
        List<Map<String, Object>> list = new ArrayList<>();
        for (Object record : records) {
            Map<String, Object> parameters = ObjectMapUtils.bean2map(record);
            list.add(parameters);
        }
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        return Crud.getInstance().getCrudTemplate().executeBatch(conn, sql, CType.source, list, insertFast, batchSize, pkeys, keyValues);
    }

    @Override
    public <T> int insert(Connection conn, List<String> pkeys, Map<String, Object> keyValues, InsertStatementProvider<T> provider) {
        String sql = provider.getInsertStatement();
        Object record = provider.getRow();
        Map<String, Object> parameters = ObjectMapUtils.bean2map(record);
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        return Crud.getInstance().getCrudTemplate().insert(conn, pkeys, keyValues, sql, CType.source, parameters);
    }

    @Override
    public int insert(Connection conn, List<String> pkeys, Map<String, Object> keyValues, GeneralInsertStatementProvider provider) {
        String sql = provider.getInsertStatement();
        Map<String, Object> parameters = provider.getParameters();
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        return Crud.getInstance().getCrudTemplate().insert(conn, pkeys, keyValues, sql, CType.source, parameters);
    }

    @Override
    public int insertSelect(Connection conn, List<String> pkeys, Map<String, Object> keyValues, InsertSelectStatementProvider provider) {
        String sql = provider.getInsertStatement();
        Map<String, Object> parameters = provider.getParameters();
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        return Crud.getInstance().getCrudTemplate().insert(conn, pkeys, keyValues, sql, CType.source, parameters);
    }

    @Override
    public int update(Connection conn, UpdateStatementProvider provider) {
        String sql = provider.getUpdateStatement();
        Map<String, Object> paramters = provider.getParameters();
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        return Crud.getInstance().getCrudTemplate().execute(conn, sql, CType.source, paramters);
    }

    @Override
    public int delete(Connection conn, DeleteStatementProvider provider) {
        String sqlSource = provider.getDeleteStatement();
        Map<String, Object> paramters = provider.getParameters();
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sqlSource = SimpleSQLTemplate.unwrapper(dialect, sqlSource);
        return Crud.getInstance().getCrudTemplate().execute(conn, sqlSource, CType.source, paramters);
    }

    @Override
    public <T> Page<T> selectPage(Connection conn, long start, int limit, Class<T> beanClass, SelectStatementProvider provider) {
        String sql = provider.getSelectStatement();
        int selectIndex = sql.indexOf("select");
        int fromIndex = sql.indexOf("from");
        int lastOrderIndex = sql.lastIndexOf("order");
        StringBuilder sb = new StringBuilder();
        switch (OrmConfig.getInstance().getSqlTemplateType()) {
            case enjoy:
                sb.append(sql, 0, selectIndex + 6).append(" #pageTag()")
                        .append(sql, selectIndex + 6, fromIndex).append(" #end ");
                if (lastOrderIndex > -1) {
                    sb.append(sql, fromIndex, lastOrderIndex).append(" #pageIgnoreTag()").append(sql.substring(lastOrderIndex)).append(" #end");
                } else {
                    sb.append(sql.substring(fromIndex));
                }
                break;
            case freemarker:
                sb.append(sql, 0, selectIndex + 6).append(" <@page>")
                        .append(sql, selectIndex + 6, fromIndex).append(" </@page> ");
                if (lastOrderIndex > -1) {
                    sb.append(sql, fromIndex, lastOrderIndex).append(" <@pageIgnore>").append(sql.substring(lastOrderIndex)).append(" </@pageIgnore>");
                } else {
                    sb.append(sql.substring(fromIndex));
                }
                break;
            case mybatis:
                sb.append(sql, 0, selectIndex + 6).append(" [@page ")
                        .append(sql, selectIndex + 6, fromIndex).append(" ] ");
                if (lastOrderIndex > -1) {
                    sb.append(sql, fromIndex, lastOrderIndex).append(" [@pageIgnore ").append(sql.substring(lastOrderIndex)).append(" ]");
                } else {
                    sb.append(sql.substring(fromIndex));
                }
                break;
            default:
                break;
        }
        sql = sb.toString();
        Map<String, Object> paramters = provider.getParameters();
        paramters = new HashMap<>(paramters);
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        return Crud.getInstance().getCrudTemplate().selectPage(conn, start, limit, beanClass, sql, CType.source, paramters);
    }

    @Override
    public <T> Page<T> selectPageRaw(Connection conn, long start, int limit, Class<T> beanClass, SelectStatementProvider countProvider,
                                     SelectStatementProvider listProvider) {
        Assert.notNull(beanClass, "beanClass is null.");
        Assert.notNull(countProvider, "countSql is null.");
        Assert.notNull(listProvider, "listSql is null.");
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        String countSql = countProvider.getSelectStatement();
        Map<String, Object> countParameters = countProvider.getParameters();
        countParameters = new HashMap<>(countParameters);
        countSql = SimpleSQLTemplate.unwrapper(dialect, countSql);
        String listSql = listProvider.getSelectStatement();
        Map<String, Object> listParameters = listProvider.getParameters();
        listParameters = new HashMap<>(listParameters);
        listSql = SimpleSQLTemplate.unwrapper(dialect, listSql);
        return Crud.getInstance().getCrudTemplate().selectPageRaw(conn, start, limit, beanClass, countSql, CType.source, countParameters,
                listSql, CType.source, listParameters);
    }

    @Override
    public Object[] selectArray(Connection conn, SelectStatementProvider provider) {
        String sql = provider.getSelectStatement();
        Map<String, Object> paramters = provider.getParameters();
        paramters = new HashMap<>(paramters);
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        return Crud.getInstance().getCrudTemplate().selectArray(conn, sql, CType.source, paramters);
    }

    @Override
    public <T> List<T> selectList(Connection conn, Class<T> beanClass, SelectStatementProvider provider) {
        String sql = provider.getSelectStatement();
        Map<String, Object> paramters = provider.getParameters();
        paramters = new HashMap<>(paramters);
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        return Crud.getInstance().getCrudTemplate().selectList(conn, beanClass, sql, CType.source, paramters);
    }

    @Override
    public <T> List<T> selectList(Connection conn, Class<T> beanClass, SelectStatementProvider provider, long start, int limit) {
        String sql = provider.getSelectStatement();
        Map<String, Object> paramters = provider.getParameters();
        paramters = new HashMap<>(paramters);
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        String pageSql = dialect.sqlPageList(new StringBuilder(sql), start, limit).toString();
        return Crud.getInstance().getCrudTemplate().selectList(conn, beanClass, pageSql, CType.source, paramters);
    }

    @Override
    public <T> T selectOne(Connection conn, Class<T> beanClass, SelectStatementProvider provider) {
        String sql = provider.getSelectStatement();
        Map<String, Object> paramters = provider.getParameters();
        paramters = new HashMap<>(paramters);
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        return Crud.getInstance().getCrudTemplate().selectOne(conn, beanClass, sql, CType.source, paramters);
    }

    @Override
    public List<Map<String, Object>> select(Connection conn, SelectStatementProvider provider) {
        String sql = provider.getSelectStatement();
        Map<String, Object> paramters = provider.getParameters();
        paramters = new HashMap<>(paramters);
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        return Crud.getInstance().getCrudTemplate().select(conn, sql, CType.source, paramters);
    }

    @Override
    public <T> void selectIterator(Connection conn, Consumer<Iterable<T>> ormIt, Class<T> returnClass, SelectStatementProvider provider) {
        String sql = provider.getSelectStatement();
        Map<String, Object> paramters = provider.getParameters();
        paramters = new HashMap<>(paramters);
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        Crud.getInstance().getCrudTemplate().selectIterator(conn, ormIt, returnClass, sql, CType.source, paramters);
    }

    @Override
    public <T> void selectStream(Connection conn, Consumer<Stream<T>> ormStream, Class<T> returnClass, SelectStatementProvider provider) {
        String sql = provider.getSelectStatement();
        Map<String, Object> paramters = provider.getParameters();
        paramters = new HashMap<>(paramters);
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        Crud.getInstance().getCrudTemplate().selectStream(conn, ormStream, returnClass, sql, CType.source, paramters);
    }
}
